/**
 * Copyright (c) 2012 Nokia Corporation.
 * All rights reserved.
 *
 * For the applicable distribution terms see the license text file included in
 * the distribution.
 */

import QtQuick 1.1
import QtMobility.location 1.2
import "CommonConstants.js" as Constants

Item {
    id: placesMap

    property Landmark selectedLandmark

    signal bubbleClicked()
    signal bubbleLongPress()

    /*!
      Clears the previous route if one exists and creates a request to draw
      a new one based on the given coordinates.
    */
    function drawRoute(latitude, longitude)
    {
        // Clear previous route.
        map.removeMapObject(map.routeLine);
        map.routeLine.destroy();

        // Create dynamically new route line object and add it to the map.
        map.routeLine = Qt.createQmlObject(
                    "import QtQuick 1.1; import QtMobility.location 1.2; "
                    + "MapPolyline {z: 1; border.width: 4; border.color: "
                    + "\"green\"; opacity: 1;}",
                    map, "routeLine");
        map.addMapObject(map.routeLine);

        routeManager.requestRoute(positionSource.position.coordinate.latitude,
                                  positionSource.position.coordinate.longitude,
                                  latitude, longitude);
    }

    /*!
      Moves the location indicator and pans to \a coordinate.
    */
    function updateCoordinates(coordinate)
    {
        panToCoordinate(coordinate);
        moveHereToCoordinate(coordinate, 300);
    }

    /*!
      Moves the red circle to given coordinate, sets the radius of the circle
      by the given accuracy. If list view is visible, skip animation.
    */
    function moveHereToCoordinate(coordinate, accuracyInMeters)
    {
        if (main.listViewVisible) {
            console.debug("PlacesMap.qml: moveHereToCoordinate(): listViewVisible == true");
            mapCircle.center.latitude = coordinate.latitude;
            mapCircle.center.longitude = coordinate.longitude;
        }
        else {
            hereCenterAnimation.latitude = coordinate.latitude;
            hereCenterAnimation.longitude = coordinate.longitude;
            hereCenterAnimation.accuracyInMeters = accuracyInMeters;
            hereCenterAnimation.restart();
        }
    }

    /*!
      Pans the map to the given coordinate. If list view is visible, skip the
      animation.
    */
    function panToCoordinate(coordinate)
    {
        if (main.listViewVisible) {
            console.debug("PlacesMap.qml: panToCoordinate(): listViewVisible == true");
            map.center.latitude = coordinate.latitude;
            map.center.longitude = coordinate.longitude;
        }
        else {
            panningAnimation.latitude = coordinate.latitude;
            panningAnimation.longitude = coordinate.longitude;
            panningAnimation.restart();
        }
    }

    /*!
      Changes here icon bitmap to inform user that position source has
      been initialized.
     */
    function positionSourceInitialized()
    {
        mapImageBlinkingTimer.stop();
        mapImage.visible = true;
        mapImage.source = "qrc:/here-icon.png";
    }

    onHeightChanged: {
        // On orientation change move bubble to correct position.
        if (bubble.opacity > 0) {
            var screenPos = map.toScreenPosition(selectedLandmark.coordinate);
            bubble.x = screenPos.x;
            bubble.y = screenPos.y - bubble.height;
        }
    }

    // Landmark model with a proximity filter to narrow down the closest
    // places.
    LandmarkModel {
        id: landmarkModel
        autoUpdate: true
        limit: 25 // Maximum number of places to show at once

        filter: LandmarkProximityFilter {
            id: landmarkProximityFilter
            center: positionSource.position.coordinate
            radius: 2000
        }

        onLandmarksChanged: {
            console.log("PlacesMap.qml: landmarkModel::onLandmarksChanged: "
                        + "Landmark count is now " + count + ".");
        }
    }

    // Timer for blinking the location indicator until the current position
    // is found.
    Timer {
        id: mapImageBlinkingTimer
        interval: 500
        running: true
        repeat: true
        onTriggered: mapImage.visible = !mapImage.visible;
    }

    Connections {
        target: routeManager
        onCoordinateFound: map.addCoordinate(latitude, longitude);
    }

    Map {
        id: map
        anchors.fill: parent
        z: 1

        property MapPolyline routeLine: MapPolyline {}

        /*!
          Adds a new coordinate to the route polyline.
        */
        function addCoordinate(latitude, longitude)
        {
            var newCoordinate = Qt.createQmlObject(
                        "import QtQuick 1.1; import QtMobility.location 1.2; "
                        + "Coordinate {latitude: " + latitude
                        + "; longitude: " + longitude + ";}",
                        routeLine, "coord");
            routeLine.addCoordinate(newCoordinate);
        }

        plugin : Plugin { name : "nokia" }
        zoomLevel: 14
        connectivityMode: Map.OnlineMode
        mapType: Map.StreetMap

        // The initial coordinate where map opens.
        center: Coordinate {
            longitude: settings.getSetting("last_longitude");
            latitude: settings.getSetting("last_latitude");
        }

        // Circle for indicating both the position and the accuracy of the
        // positioning.
        MapCircle {
            id: mapCircle

            center: Coordinate {
                longitude: settings.getSetting("last_longitude");
                latitude: settings.getSetting("last_latitude");
            }

            color: "#40FF0000"
            border.color: Qt.darker(color)
            border.width: 3
            opacity: 1.0
        }

        MapImage {
            id: mapImage
            coordinate: mapCircle.center
            source: "qrc:/here-icon-gray.png"
            smooth: true
            offset.x: -10
            offset.y: -10
            z: 2
        }

        // Delegate for showing the places on the map.
        Component {
            id: mapObjectViewDelegate

            MapImage {
                property string name: landmark.name
                source: "qrc:/marker.png"
                smooth: true
                offset.x: -18
                offset.y: -53
                coordinate: landmark.coordinate

                MapMouseArea {
                    anchors.fill: parent

                    onClicked: {
                        // Store the selected landmark and move/show the bubble.
                        selectedLandmark = landmark;
                        bubble.move(mouseX, mouseY - bubble.height, 280, 95,
                                    selectedLandmark.name);
                    }
                }
            }
        }

        // The MapObjectView instance for showing the landmarks on the map.
        MapObjectView {
            id: landmarks
            model: landmarkModel
            delegate: mapObjectViewDelegate
        }

        Bubble {
            id: bubble
            width: 180
            height: 75
            z: 1
            opacity: 0

            Behavior on opacity { NumberAnimation { duration: 200 } }

            onClicked: placesMap.bubbleClicked();
            onLongPress: placesMap.bubbleLongPress();

            onMoveComplete: {
                var screenPos = map.toScreenPosition(selectedLandmark.coordinate);

                // Move slightly to the left to leave room for the bubble.
                screenPos.x += Constants.BubbleOffset;
                panToCoordinate(map.toCoordinate(screenPos));
            }
        }
    }

    PinchArea {
        property double oldZoomLevel

        function calcZoomDelta(zoom, percent)
        {
            var zoomLevel = zoom + Math.log(percent) / Math.log(2);
            zoomLevel = Math.round(zoomLevel);

            if (zoomLevel > map.maximumZoomLevel) {
                zoomLevel = map.maximumZoomLevel;
            }
            else if (zoomLevel < map.minimumZoomLevel) {
                zoomLevel = map.minimumZoomLevel;
            }

            return zoomLevel;
        }

        anchors.fill: parent

        onPinchStarted: oldZoomLevel = map.zoomLevel;

        onPinchUpdated: {
            if (oldZoomLevel >= map.maximumZoomLevel
                    && pinch.scale > 1.0)
            {
                // Don't allow the user to zoom more than the map supports
                return;
            }
            else if (oldZoomLevel <= map.minimumZoomLevel
                     && pinch.scale < 1.0) {
                // Don't allow the user to zoom less than the map supports
                return;
            }

            map.scale = pinch.scale;
        }

        onPinchFinished: {
            map.zoomLevel = calcZoomDelta(oldZoomLevel, pinch.scale);
            map.scale = 1;
            landmarkModel.update();
        }
    }

    MouseArea {
        id: panMouseArea

        property bool mouseDown: false
        property int lastX: -1
        property int lastY: -1

        anchors.fill: parent

        onPressed: {
            panningAnimation.stop();
            mouseDown = true;
            lastX = mouse.x;
            lastY = mouse.y;
        }
        onReleased: {
            if (bubble.opacity > 0) {
                bubble.opacity = 0;
            }
            mouseDown = false;
            lastX = -1;
            lastY = -1;
        }
        onPositionChanged: {
            if (mouseDown) {
                var dx = mouse.x - lastX;
                var dy = mouse.y - lastY;
                map.pan(-dx, -dy);
                lastX = mouse.x;
                lastY = mouse.y;

                var bubbleX = bubble.x;
                var bubbleY = bubble.Y;
                bubble.x += dx;
                bubble.y += dy;
            }
        }
        onCanceled: {
            mouseDown = false;
        }
    }

    ParallelAnimation {
        id: hereCenterAnimation

        property real latitude
        property real longitude
        property real accuracyInMeters

        PropertyAnimation {
            target: mapCircle
            property: "center.latitude"
            to: hereCenterAnimation.latitude
            easing.type: Easing.InOutCubic
            duration: 1000
        }

        PropertyAnimation {
            target: mapCircle
            property: "center.longitude"
            to: hereCenterAnimation.longitude
            easing.type: Easing.InOutCubic
            duration: 1000
        }

        PropertyAnimation {
            target: mapCircle
            property: "radius"
            to: hereCenterAnimation.accuracyInMeters
            easing.type: Easing.InOutCubic
            duration: 1000
        }
    }

    // An animation for centering the map to a given coordinate.
    // The animation moves also the map item bubble if visible.
    ParallelAnimation {
        id: panningAnimation

        property real latitude
        property real longitude

        property Coordinate coordinate: Coordinate {}

        onLatitudeChanged: coordinate.latitude = latitude;
        onLongitudeChanged: coordinate.longitude = longitude;

        // Constants
        property int panningDuration: 800
        property int easingType: Easing.InOutCubic

        PropertyAnimation {
            target: map
            property: "center.latitude"
            to: panningAnimation.latitude
            easing.type: panningAnimation.easingType
            duration: panningAnimation.panningDuration
        }
        PropertyAnimation {
            target: map
            property: "center.longitude"
            to: panningAnimation.longitude
            easing.type: panningAnimation.easingType
            duration: panningAnimation.panningDuration
        }
        PropertyAnimation {
            target: bubble
            property: "x"
            to: map.toScreenPosition(map.center).x - Constants.BubbleOffset
            easing.type: panningAnimation.easingType
            duration: bubble.opacity > 0 ? panningAnimation.panningDuration : 0;
        }
        PropertyAnimation {
            target: bubble
            property: "y"
            to: map.toScreenPosition(map.center).y - bubble.height
            easing.type: panningAnimation.easingType
            duration: bubble.opacity > 0 ? panningAnimation.panningDuration : 0;
        }
    }
}
